﻿Public Class ObjectChangeManager
#Region "Singleton"
    Private Shared mInstance As ObjectChangeManager
    Public Shared ReadOnly Property Instance As ObjectChangeManager
        Get
            If mInstance Is Nothing Then
                mInstance = New ObjectChangeManager
            End If
            Return mInstance
        End Get
    End Property
#End Region

    Private mAllListeners As Dictionary(Of String, List(Of WeakReference(Of IObjectChangeListener)))
    Private mCleanupTimer As System.Threading.Timer

    Private Sub New()
        mAllListeners = New Dictionary(Of String, List(Of WeakReference(Of IObjectChangeListener)))
        mCleanupTimer = New System.Threading.Timer(AddressOf CleanupWeakReferences, Nothing, TimeSpan.FromMinutes(5), TimeSpan.FromMilliseconds(-1))
    End Sub

    Public Sub RegisterForChangeNotice(obj As RawObject, handler As IObjectChangeListener)
        SyncLock mAllListeners
            Dim objectListeners As List(Of WeakReference(Of IObjectChangeListener)) = Nothing
            If Not mAllListeners.TryGetValue(obj.UniqueKey, objectListeners) Then
                objectListeners = New List(Of WeakReference(Of IObjectChangeListener))
                mAllListeners.Add(obj.UniqueKey, objectListeners)
            End If
            Dim ref As New WeakReference(Of IObjectChangeListener)(handler)
            objectListeners.Add(ref)
        End SyncLock
    End Sub

    Public Sub NotifyObjectChanged(obj As RawObject, reloadTokens As Boolean, source As Object)
        SyncLock mAllListeners
            Dim objectListeners As List(Of WeakReference(Of IObjectChangeListener)) = Nothing
            If mAllListeners.TryGetValue(obj.UniqueKey, objectListeners) Then
                Dim args As New ObjectChangeEventArgs(obj, source) With {.ReloadTokens = reloadTokens}
                For i As Integer = objectListeners.Count - 1 To 0 Step -1
                    Dim ref As WeakReference(Of IObjectChangeListener) = objectListeners(i)
                    Dim listener As IObjectChangeListener = Nothing
                    If ref.TryGetTarget(listener) Then
                        listener.ObjectChanged(args)
                    Else
                        'The listener no longer exists, remove it
                        objectListeners.Remove(ref)
                    End If
                Next
            End If
        End SyncLock
    End Sub

    Private Sub CleanupWeakReferences(state As Object)
        Dim deadObjects As New List(Of String)
        SyncLock mAllListeners
            For Each key As String In mAllListeners.Keys
                Dim objectListeners As List(Of WeakReference(Of IObjectChangeListener)) = mAllListeners(key)
                For i As Integer = objectListeners.Count - 1 To 0 Step -1
                    Dim ref As WeakReference(Of IObjectChangeListener) = objectListeners(i)
                    Dim listener As IObjectChangeListener = Nothing
                    If Not ref.TryGetTarget(listener) Then
                        'The listener no longer exists, remove it
                        objectListeners.Remove(ref)
                    End If
                Next
                If objectListeners.Count = 0 Then
                    deadObjects.Add(key)
                End If
            Next
            For Each key As String In deadObjects
                mAllListeners.Remove(key)
            Next
        End SyncLock
        mCleanupTimer.Change(TimeSpan.FromMinutes(5), TimeSpan.FromMilliseconds(-1))
    End Sub
End Class
